
class Individual extends Entity {

// PARAMETERS
  final float probOfTwitch = 0.001;
  final float twitchVectorSize = 0.5;

  final float minSize = 30.0;
  final float maxSize = 55.0;
  final float defaultSize = minSize;

  final float sizeDecreasePerFrame = 0.01;
  final float sizeUpPerSpread = 10.0;

  final color strokeColorPassive = color(63,63,63);

  final color fillColorPassive = color(235, 232, 238);
  final color fillColorSpreading = color(251, 250, 240);
  
  final int thicknessLastMeme = 2;
  final int thicknessPassive = 3;
  final int thicknessSpreading = 4;

  final float infectRingMaxOpacity = 200;
  final float infectForceStr = 5.0;

  final int maxConnections = 5;
  final float probAnotherSpread = 0.4;

  final int leastFrameUntilNextSpread = 5;
  final int mostFrameUntilNextSpread = 30;

  final float glowRadius = 10;

  // penalty/reward for infection success/fail
  final float currencyIncrease = 0.1;
  final float currencyDecrease = 0.3;

  final int spreadAnimFrames = 25;


  final float memeAfterGlowFrames = 500;

  final float pulseMaxSize = 4.0;
  final float pulseMinSize = -2.0;
  final float pulseSpeed = 0.05;
// PARAMETERS END


// MEMBERS
  SocialGroup parent;
  // the actively spreading meme, the incoming meme from a
  // connection and the last spread meme
  Meme activeMeme, incomingMeme, lastMeme;

  // countdown until next spread

  // controls how many times the activeMeme have been spread
  boolean isSpreading = false;

  // number of (outgoing) connections to other individuals
  int createdConnections = 0;

  // keep track of connections
  Vector connections;

  float socialCurrency = 0.33;

  private Vector memes;

  public float angle;
  public float distance;

  private float spreadAnimFrame = 0;
  float framesUntilNextSpread = 0;
  float funsStart = 0;
  float memeAfterGlow = 0;

  float pulse = 0.0;
  float pulseChange = pulseSpeed;
// MEMBERS END

  public Individual(SocialGroup parent) {
    super();

    this.parent = parent;
    // mass here affects the sluggishness of the individuals movement
    p = ps.makeParticle(2.0, parent.absx(), parent.absy(), 0); 

    memes = new Vector();
    connections = new Vector();
    
    socialCurrency = fixedRandom(0.2, 0.5);

    pulse = random(pulseMinSize, pulseMaxSize);

    setSize();
  }

  public boolean hasMeme(Meme meme) {
    for(int i=0; i < memes.size(); i++)
      if(((Meme)memes.get(i)) == meme) 
        return true;

    return incomingMeme == meme;
  }

  public void drawMicro() {

    float ratio;
    color strokeCol, fillCol;

    float renderSize = size + pulse;

    // draw a glow
    color glowColor = 
      (activeMeme == null ? strokeColorPassive: activeMeme.c);
    noStroke();
    fill(glowColor, 10);
    ellipse(x(), y(), renderSize + glowRadius,renderSize + glowRadius);    
    fill(glowColor, 40);
    ellipse(x(), y(), renderSize + glowRadius * 0.3, renderSize + glowRadius * 0.3);    

    // coloring order, spreading meme, last meme, idle
    if(activeMeme != null) {
      strokeCol = (0);
      fillCol = fillColorSpreading;
    }
    else if (lastMeme != null) {
      fillCol = 
        mergeColor(fillColorPassive, lastMeme.c, 
          memeAfterGlow / memeAfterGlowFrames);
      strokeCol = strokeColorPassive;
    }
    else {
      strokeCol = strokeColorPassive;
      fillCol = fillColorPassive;
    }

    // draw the spread radius (outward)
    if(spreadAnimFrame > 0) {
      ratio = spreadAnimFrame / spreadAnimFrames;
      noFill();
      strokeWeight(1 + 8 * ratio);

      stroke(
          mergeColor(backgroundColor, 
             strokeCol, ratio));
      
      ellipse(x(), y(), 
          renderSize + renderSize * (1 - ratio),
          renderSize + renderSize * (1 - ratio));
    }

    strokeWeight(1); 
    stroke(strokeCol);
    fill(fillCol);
    ellipse(x(), y(), renderSize, renderSize);

    // draw the infection radius (inside)
    if(framesUntilNextSpread > 0) {
      ratio = framesUntilNextSpread / funsStart;
      strokeWeight((1 - ratio));

      stroke(
          mergeColor(backgroundColor, 
            ((Meme)memes.get(memes.size() - 1)).c, ratio));
      
      fill(((Meme)memes.get(memes.size() - 1)).c);

      ellipse(x(), y(), 
          renderSize * (1 - ratio),
          renderSize * (1 - ratio));
    }

    image(indImage, x() - renderSize/2, y() - renderSize/2, renderSize, renderSize);

  }

  public void drawMacro() {
    stroke(mergeColor(backgroundColor, strokeColorPassive, 0.5));
    if(lastMeme != null) {
      fill(mergeColor(
            fillColorPassive, lastMeme.c, 
            memeAfterGlow / memeAfterGlowFrames));
    }
    else
      fill(fillColorPassive);

    strokeWeight(1);
    ellipse(x(), y(), size, size);
  }

  private void setSize() {
    size = minSize + (maxSize - minSize) * socialCurrency;
  }

  public void reward() {
    socialCurrency += currencyIncrease;
    socialCurrency = socialCurrency > 1.0 ? 1.0 : socialCurrency;
    setSize();
  }

  public void punish() {
    socialCurrency -= currencyDecrease;
    socialCurrency = socialCurrency < 0.0 ? 0 : socialCurrency;
    setSize();
  }

  public boolean infect(
      Individual source, 
      Meme meme, 
      float connectionSpreadBonus) {

    if(meme == null) println("Individual MEME NULL");

    // remember this meme
    memes.add(meme);
    incomingMeme = null;

    float infectRisk = 
      // senders social capital
      source.socialCurrency + 
      // bonus for spreading through existing connections
      connectionSpreadBonus +  
      // the memes own spread bonus (enables supermemes)
      meme.spreadBonus;

    float dx = x() - source.x();
    float dy = y() - source.y();
    float a = atan2(dx, dy);

    if(fixedRandom(1.0) < infectRisk) {
      // infected!
      source.spreadAnimFrame = spreadAnimFrames;

      p.addVelocity(
          -infectForceStr * sin(a),
          -infectForceStr * cos(a), 0);
      source.p.addVelocity(
          infectForceStr * sin(a), 
          infectForceStr * cos(a), 0);
      
      // reward infection source
      source.reward();

      // start the meme afterglow
      memeAfterGlow = memeAfterGlowFrames;

      // spread on the meme
      queueSpread(meme);
      return true;
    }

    else {
      // dont include this individual as a spreader
      meme.removeSpreader(this);

      p.addVelocity(
          infectForceStr * sin(a),
          infectForceStr * cos(a), 0);
      source.p.addVelocity(
          -infectForceStr * sin(a), 
          -infectForceStr * cos(a), 0);

      // affect connection?
      source.punish();
      return false;
    }

  }

  public void update() {

    if(isSpreading) {
      if((framesUntilNextSpread -= timeScale) < 0) {

        // spread meme
        parent.requestSpreadChannel(this, activeMeme);

        // decide to spread again or not
        isSpreading = (random(0.0, 1.0) < probAnotherSpread);

        // dont spread active meme
        if(!isSpreading) {
          activeMeme.removeSpreader(this);

          // hold a reference for color
          lastMeme = activeMeme;
          activeMeme = null;
        }
        else {
        framesUntilNextSpread = fixedRandom(
          leastFrameUntilNextSpread, 
          mostFrameUntilNextSpread);
        funsStart = framesUntilNextSpread;
        }
      }
    }

    // after glow, and remove meme after its done
    if(memeAfterGlow > 0) {
      memeAfterGlow -= timeScale;
      if(memeAfterGlow < 0) {
        lastMeme = null;
      }
    }

    // update the spread ring
    if(spreadAnimFrame > 0)
      spreadAnimFrame -= timeScale;

    pulse +=  pulseChange;

    if(pulse > pulseMaxSize) {
      pulseChange *= -1;
      pulse = pulseMaxSize;
    }

    if(pulse < pulseMinSize) {
      pulseChange *= -1;
      pulse = pulseMinSize;
    }

    // test for twitch
    if(random(0.0, 1.0) < probOfTwitch)
      p.addVelocity(random(-twitchVectorSize, twitchVectorSize), 
          random(-twitchVectorSize, twitchVectorSize), 0);
  }


  private void queueSpread(Meme meme) {
    isSpreading = true;
    activeMeme = meme;
    lastMeme = activeMeme;

    // add this individual as a definite spreader

    framesUntilNextSpread = 
      fixedRandom(leastFrameUntilNextSpread, mostFrameUntilNextSpread);
    funsStart = framesUntilNextSpread;
  }
}
